﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading;
using DoNet.Utils;

namespace DoNet.TenPay
{
    public class ResponseHandlerV3
    {
        /// <summary>
        /// 请求URL
        /// </summary>
        public string RequestUrl = "";
        /// <summary>
        /// 微信支付的平台证书序列号
        /// </summary>
        public string Serial = "";
        /// <summary>
        /// 微信支付的应答签名
        /// </summary>
        public string Signature = "";
        /// <summary>
        /// 应答时间戳
        /// </summary>
        public string Timestamp = "";
        /// <summary>
        /// 应答随机串
        /// </summary>
        public string Nonce = "";

        /// <summary>
        /// 应答主体（response Body）
        /// </summary>
        public string ResponseBody = "";

        /// <summary>
        /// 错误时 应答主体（response Body）
        /// </summary>
        public PayV3Result ErrorResponseBody = null;
        /// <summary>
        /// 构造验签名串
        /// </summary>
        public string SignSource = "";
        /// <summary>
        /// 验证签名是否通过 默认不需要签名
        /// </summary>
        public bool IsVerifySign = false;
        /// <summary>
        /// 应答返回是否成功
        /// </summary>
        public bool IsResponse = false;
        /// <summary>
        /// 返回数据 bytes
        /// </summary>
        public byte[] ResponseBytes = null;

        /// <summary>
        /// 获取请求结果
        /// </summary>
        /// <param name="url">微信的接口地址</param>
        /// <param name="merchantId">发起请求的商户（包括直连商户、服务商或渠道商）的商户号 mchid</param>
        /// <param name="cert">微信支付API证书</param>
        /// <param name="method">默认为GET，请求方式 GET、HEAD、POST、PUT、DELETE、TRACE 或 OPTIONS等
        /// HTTP使用HEAD方法请求时，服务器只返回HTTP报头，不返回文档主体，因而可以用它来获取远程文件大小：
        /// </param>
        /// <param name="postData">默认为空，POST/PUT请求的数据，json格式 GET时 置为空</param>
        /// <param name="fileDictionary"></param>
        /// <param name="isCheckSign">是否需要验证签名，默认false 不需要验证签名</param>
        /// <param name="apiKeyV3">API密钥V3 isCheckSign为true时，必填</param>
        /// <param name="contentType">发送端（客户端|服务器）发送的实体数据的数据类型。</param>
        /// <param name="accept">发送端（客户端）希望接受的数据类型</param>
        /// <param name="encoding"></param>
        /// <param name="timeout">默认为6秒，请求多少秒超时</param>
        public ResponseHandlerV3(string url, string merchantId, string method = "GET", string postData = null, Dictionary<string, string> fileDictionary = null, bool isCheckSign = false, string apiKeyV3 = "", string contentType = null, string accept = null, Encoding encoding = null, int timeout = 6)
        {
            GC.Collect();

            this.RequestUrl = url;

            HttpWebRequest request = null;

            //请求url以获取数据
            try
            {
                method = method.ToUpper();
                request = (HttpWebRequest)WebRequest.Create(url);
                request.Method = method;
                request.Timeout = timeout * 1000;

                if (string.IsNullOrEmpty(accept))
                {
                    accept = "application/json";
                }
                request.Accept = accept;

                if (string.IsNullOrEmpty(contentType))
                {
                    contentType = "application/json"; //默认为json
                }
                if (encoding == null)
                {
                    encoding = Encoding.UTF8; //默认编码
                    contentType += ";charset=UTF-8";
                }
                request.ContentType = contentType;

                //发起请求的商户（包括直连商户、服务商或渠道商）的商户号 mchid
                string USER_AGENT = string.Format("TenPayV3/{3} ({0}) .net/{1} {2}", Environment.OSVersion, Environment.Version, merchantId, this.GetType().Assembly.GetName().Version);
                request.UserAgent = USER_AGENT;
                string Authorization = SignatureV3.GetAuthorization(url, method, postData, merchantId);
                request.Headers.Add("Authorization", Authorization);

                #region 将参数转换成流
                MemoryStream postStream = null;
                if (!string.IsNullOrEmpty(postData))
                {
                    byte[] paramJsonBytes = encoding.GetBytes(postData);
                    postStream = new MemoryStream(paramJsonBytes);
                }
                #endregion

                #region 处理Form表单文件上传
                var formUploadFile = fileDictionary != null && fileDictionary.Count > 0;//是否用Form上传文件
                if (formUploadFile)
                {
                    //通过表单上传文件
                    string boundary = "----" + DateTimeOffset.Now.Ticks.ToString("x");

                    postStream = postStream ?? new MemoryStream();
                    //byte[] boundarybytes = Encoding.ASCII.GetBytes("\r\n--" + boundary + "\r\n");
                    string fileFormdataTemplate = "\r\n--" + boundary + "\r\nContent-Disposition: form-data; name=\"{0}\"; filename=\"{1}\"\r\nContent-Type: application/octet-stream\r\n\r\n";
                    string dataFormdataTemplate = "\r\n--" + boundary + "\r\nContent-Disposition: form-data; name=\"{0}\"\r\n\r\n{1}";

                    foreach (var file in fileDictionary)
                    {
                        try
                        {
                            var fileNameOrFileData = file.Value;

                            string formdata = null;
                            using (var memoryStream = new MemoryStream())
                            {
                                //fileNameOrFileData 中储存的可能是文件地址或备注
                                //准备文件流
                                using (var fileStream = FileUtil.GetFileStream(fileNameOrFileData))
                                {
                                    if (fileStream != null)
                                    {
                                        //存在文件
                                        memoryStream.Seek(0, SeekOrigin.Begin);
                                        fileStream.CopyTo(memoryStream);
                                        formdata = string.Format(fileFormdataTemplate, file.Key, Path.GetFileName(fileNameOrFileData));
                                    }
                                    else
                                    {
                                        //不存在文件或只是注释
                                        formdata = string.Format(dataFormdataTemplate, file.Key, file.Value);
                                    }
                                }

                                //统一处理
                                var formdataBytes = encoding.GetBytes(postStream.Length == 0 ? formdata.Substring(2, formdata.Length - 2) : formdata);//第一行不需要换行
                                postStream.Write(formdataBytes, 0, formdataBytes.Length);

                                //写入文件
                                if (memoryStream.Length > 0)
                                {
                                    memoryStream.Seek(0, SeekOrigin.Begin);

                                    byte[] buffer = new byte[1024];
                                    int bytesRead = 0;
                                    while ((bytesRead = memoryStream.Read(buffer, 0, buffer.Length)) != 0)
                                    {
                                        postStream.Write(buffer, 0, bytesRead);
                                    }
                                }
                            }
                        }
                        catch (Exception ex)
                        {
                            throw ex;
                        }
                    }
                    //结尾
                    var footer = encoding.GetBytes("\r\n--" + boundary + "--\r\n");
                    postStream.Write(footer, 0, footer.Length);

                    request.ContentType = string.Format("multipart/form-data; boundary={0}", boundary);
                }
                #endregion

                request.ContentLength = postStream != null ? postStream.Length : 0;

                #region 输入二进制流
                if (postStream != null && postStream.Length > 0)
                {
                    postStream.Position = 0;

                    //往服务器写入数据
                    using (Stream requestStream = request.GetRequestStream())
                    {
                        byte[] buffer = new byte[1024];
                        int bytesRead = 0;
                        while ((bytesRead = postStream.Read(buffer, 0, buffer.Length)) != 0)
                        {
                            requestStream.Write(buffer, 0, bytesRead);
                        }
                    }

                    postStream.Close();//关闭文件访问
                }
                #endregion

                HttpWebResponse response = null;
                var body = "";
                try
                {
                    //获取服务器返回
                    response = (HttpWebResponse)request.GetResponse();
                    //获取HTTP返回数据
                    using (Stream stream = response.GetResponseStream())
                    {
                        this.ResponseBytes = FileUtil.StreamToBytes(stream);
                        using (StreamReader reader = new StreamReader(stream, encoding))
                        {
                            body = reader.ReadToEnd().Trim();
                            this.ResponseBody = body;
                        }
                    }
                }
                catch (WebException e)
                {
                    //Log.Error("ResponseHandler", e.ToString());
                    response = (HttpWebResponse)e.Response;
                    using (Stream resStream = response.GetResponseStream())
                    {
                        using (StreamReader reader = new StreamReader(resStream, encoding))
                        {
                            body = reader.ReadToEnd().Trim();
                            this.ErrorResponseBody = JsonUtil.JsonToT<PayV3Result>(body);
                        }
                    }
                }
                finally
                {
                    this.Serial = response.Headers.GetValues("Wechatpay-Serial").FirstOrDefault();
                    this.Signature = response.Headers.GetValues("Wechatpay-Signature").FirstOrDefault();
                    this.Timestamp = response.Headers.GetValues("Wechatpay-Timestamp").FirstOrDefault();
                    this.Nonce = response.Headers.GetValues("Wechatpay-Nonce").FirstOrDefault();
                    //构造验签名串
                    this.SignSource = this.Timestamp + "\n" + this.Nonce + "\n" + body + "\n";

                    //OK 等效于 HTTP 状态 200。OK 指示请求成功，且请求的信息包含在响应中。这是最常接收的状态代码。
                    //NoContent 等效于 HTTP 状态 204。NoContent 指示已成功处理请求并且响应已被设定为无内容。
                    if (response.StatusCode == HttpStatusCode.OK || response.StatusCode == HttpStatusCode.NoContent)
                    {
                        this.IsResponse = true;
                    }

                    //是否需要验证签名
                    if (isCheckSign)
                    {
                        this.IsVerifySign = SignatureV3.VerifySign(this.Signature, this.SignSource, this.Serial, merchantId, apiKeyV3);
                    }

                    //关闭连接和流
                    if (response != null)
                    {
                        response.Close();
                        response.Dispose();
                    }
                }
            }
            catch (ThreadAbortException e)
            {
                Log.Error("ResponseHandlerV3", "Thread - caught ThreadAbortException - resetting.");
                Log.Error("Exception message: {0}", e.Message);
                Thread.ResetAbort();
            }
            catch (Exception e)
            {
                Log.Error("ResponseHandlerV3", e.ToString());
                //throw new WxPayException(e.ToString());
                //Log.Error("ResponseHandler", "RequestUrl：" + this.RequestUrl);
            }
            finally
            {
                //关闭连接和流
                if (request != null)
                {
                    request.Abort();
                }
            }
        }
    }
}
